package com.tx.core.tree.util;

import cn.hutool.core.util.ObjectUtil;
import com.tx.core.exceptions.util.AssertUtils;
import com.tx.core.tree.model.TreeEntity;
import com.tx.core.tree.model.TreeEntityAdapter;
import com.tx.core.tree.model.TreeEntityProxy;

import java.io.Serializable;
import java.util.*;

/**
 * <树转换工具类>
 * <功能详细描述>
 *
 * @author PengQingyang
 * @version [版本号, 2012-10-5]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
public class TreeEntityUtils {

    /**
     * 根据传入对象列表和适配器，生成代理树节点对象树
     * 该方法通过遍历传入的对象列表，并使用提供的适配器为每个对象创建一个代理树节点对象，
     * 最终将所有代理树节点对象收集到一个列表中返回。
     *
     * @param objectList 待转换为代理树节点列表的原始对象列表。该列表中的对象类型应与适配器的转换目标类型相匹配。
     * @param adapter    用于将原始对象转换为代理树节点对象的适配器。适配器必须实现TreeEntityAdapter接口。
     * @return 返回一个包含代理树节点对象的列表。如果输入的对象列表为null，则返回null。
     */
    public static <ID extends Serializable, T> List<TreeEntityProxy<ID, T>> changeToProxyTreeList(
            List<T> objectList, TreeEntityAdapter<ID, T> adapter) {
        AssertUtils.notNull(adapter, "adapter is null.");
        // 检查输入对象列表是否为null，若为null直接返回null
        if (objectList == null) {
            return null;
        }
        // 使用Stream API遍历对象列表，为每个对象创建一个代理树节点对象，并收集到一个新的列表中
        List<TreeEntityProxy<ID, T>> proxyList = new ArrayList<>();
        for(T object : objectList){
            proxyList.add(new TreeEntityProxy(adapter, object));
        }
        return changeToTree(proxyList);
    }


    /**
     * 根据传入对象列表和适配器，生成代理树节点对象树。<br/>
     * 这个方法通过迭代对象列表，并使用提供的适配器将每个对象转换为树节点代理对象。通过限制树节点的迭代层数，可以生成指定深度的树结构。
     *
     * @param objectList    要转换为树结构的对象列表。
     * @param adapter       用于将对象转换为树节点代理的适配器。
     * @param maxLevelIndex 允许转换的最大层数，-1表示不限制。
     * @return 返回一个树节点代理对象的列表，代表生成的树结构。
     */
    public static <ID extends Serializable, T> List<TreeEntityProxy<ID, T>> changeToProxyTreeList(
            List<T> objectList, TreeEntityAdapter<ID, T> adapter, int maxLevelIndex) {
        AssertUtils.notNull(adapter, "adapter is null.");
        // 检查输入对象列表是否为空，若为空则直接返回null
        if (objectList == null) {
            return null;
        }
        // 使用stream将每个对象转换为树节点代理对象，并收集到列表中
        List<TreeEntityProxy<ID, T>> proxyList = new ArrayList<>();
        for(T object : objectList){
            proxyList.add(new TreeEntityProxy(adapter, object));
        }
        // 调用changeToTree方法，将代理对象列表转换为树结构
        return changeToTree(proxyList, maxLevelIndex);
    }


    /**
     * 根据对象集合生成树节点代理集合
     * 该方法通过遍历给定的对象集合，为每个对象创建一个树节点代理，进而生成一个树状结构的代理集合。
     * 这个过程需要一个适配器（TreeEntityAdapter）来将原始对象转换为树节点代理对象。
     *
     * @param objectList 原始对象集合，这些对象将被转换成树节点代理。
     * @param adapter 适配器对象，用于将原始对象转换为树节点代理对象。适配器必须实现TreeEntityAdapter接口。
     * @return 返回一个树节点代理集合，这个集合中的每个元素都代表一个原始对象，并且具有树节点的特性。
     * @see TreeEntityProxy 代理对象类，代表树结构中的一个节点。
     * @see TreeEntityAdapter 适配器接口，用于将原始对象转换为树节点代理。
     */
    private static <ID extends Serializable, T> List<TreeEntityProxy<ID, T>> generatorTreeTypeProxyCollection(
            List<T> objectList, TreeEntityAdapter<ID, T> adapter) {
        // 初始化结果集合
        List<TreeEntityProxy<ID, T>> resCollection = new ArrayList<TreeEntityProxy<ID, T>>();
        // 遍历原始对象集合，为每个对象创建一个树节点代理并添加到结果集合中
        for (T objTemp : objectList) {
            resCollection.add(new TreeEntityProxy(adapter, objTemp));
        }
        // 返回生成的树节点代理集合
        return resCollection;
    }


    /**
     * 根据指定集合生成树<br/>
     * 首先找到不存在父节点的所有节点，将这些节点当作根节点<br/>
     * 父节点不存在于集合，以及父节点于自己的id相同的节点将被当作根节点集合的一员<br/>
     * 然后利用这些根节点迭代生成树
     *
     * @param entityList 用于生成树的集合，集合中的元素需实现TreeEntity接口
     * @return 返回生成的树的根节点列表
     */
    public static <ID extends Serializable, T extends TreeEntity<ID, T>> List<T> changeToTree(
            List<T> entityList) {
        // 调用另一个重载方法，以-1作为父节点ID标志，开始生成树的过程
        return changeToTree(entityList, -1);
    }

    /**
     * 根据指定集合生成树<br/>
     * 首先找到不存在父节点的所有节点，将这些节点当作根节点<br/>
     * 然后利用这些根节点迭代生成树<br/>
     * 允许通过参数限制生成树的层数
     *
     * @param entityList 待转换为树结构的实体列表
     * @param maxLevelIndex 限制生成树的层数，若为负数或null则不限制层数
     * @return 返回生成的树结构列表
     */
    public static <ID extends Serializable, T extends TreeEntity<ID, T>> List<T> changeToTree(
            List<T> entityList, int maxLevelIndex) {
        // 判断入参是否合法
        if (entityList == null || entityList.size() == 0) {
            // 如果root或需要转换的集合其中有一个为空，直接返回root集合当作结果
            return entityList;
        }

        List<T> parentEntityList = new ArrayList<>();

        // 循环得到当前传入list上级节点有多少个
        // 并进行非法节点过滤，如果某节点id = parentId将会造成死循环，将这样的节点抛弃
        Map<ID, List<T>> parentId2childrenMap = new HashMap<ID, List<T>>();
        Set<ID> idSet = new HashSet<ID>();
        for (T entityTemp : entityList) {
            ID parentId = entityTemp.getParentId();
            ID id = entityTemp.getId();

            idSet.add(entityTemp.getId());
            if (ObjectUtil.isEmpty(parentId) || ObjectUtil.equals(id, parentId)) {
                // 过滤掉非法节点
                parentEntityList.add(entityTemp);
                continue;
            }

            // 根据父节点形成父ID与子节点集合的映射
            if (parentId2childrenMap.containsKey(parentId)) {
                parentId2childrenMap.get(parentId).add(entityTemp);
            } else {
                List<T> childrenTemp = new ArrayList<>();
                childrenTemp.add(entityTemp);
                parentId2childrenMap.put(parentId, childrenTemp);
            }
        }

        // 迭代生成根节点集合
        for (T treeTypeTemp : entityList) {
            if (!idSet.contains(treeTypeTemp.getParentId())) {
                if(!parentEntityList.contains(treeTypeTemp)){
                    parentEntityList.add(treeTypeTemp);
                }
            }
        }
        // 通过父节点及其映射关系生成完整的树结构
        return changeToTreeByParentIdIndexMap(parentEntityList,
                maxLevelIndex,
                parentId2childrenMap);
    }


    /**
     * 根据指定根节点集合和具有parentId属性的集合生成树结构。<br/>
     * 将子节点添加到父节点的子节点集合中，构建树状结构。
     *
     * @param entityList 所有实体列表，包含根节点和子节点。
     * @param parentEntityList 根节点列表，这些节点在树中将作为顶层节点。
     * @return 构建好的树状结构列表。
     */
    public static <ID extends Serializable, T extends TreeEntity<ID, T>> List<T> changeToTree(
            List<T> entityList, List<T> parentEntityList) {
        return changToTree(entityList, parentEntityList, -1);
    }


    /**
     * 根据指定根节点集合和具有parentId的集合生成树（将子节点设置到树中）。
     * 此方法能够根据提供的实体列表和父实体列表生成一个树状结构，其中根节点集合为parentEntityList。
     * 可以通过maxLevelIndex限制生成树的最大层数。
     *
     * @param entityList         所有实体列表，这些实体应包含id和parentId属性。
     * @param parentEntityList   根节点集合，这些节点在生成的树中处于顶层。
     * @param maxLevelIndex      生成树的最大层数。如果maxLevelIndex小于等于0，则不限制层数；
     *                           如果maxLevelIndex等于1，则只返回根节点；
     *                           如果maxLevelIndex大于1，则生成相应层数的树结构。
     * @return 返回根据指定条件生成的树状结构列表。
     */
    public static <ID extends Serializable, T extends TreeEntity<ID, T>> List<T> changToTree(
            List<T> entityList, List<T> parentEntityList, int maxLevelIndex) {
        // 校验输入参数的合法性
        if (entityList == null || parentEntityList == null
                || entityList.size() == 0
                || parentEntityList.size() == 0) {
            // 如果根节点集合或需要转换的集合任一为空，直接返回根节点集合作为结果
            return parentEntityList;
        }

        // 构建父ID与子节点集合的映射关系，为构建树状结构做准备
        Map<ID, List<T>> parentIdIndexMap = new HashMap<ID, List<T>>();
        for (T treeTypeTemp : entityList) {
            ID superId = treeTypeTemp.getParentId();
            ID id = treeTypeTemp.getId();
            if (ObjectUtil.isEmpty(id) || id.equals(superId)) {
                // 过滤掉非法节点，防止因节点的id等于parentId而导致死循环
                continue;
            }

            // 父ID与子节点集合的映射关系建置
            if (parentIdIndexMap.containsKey(superId)) {
                parentIdIndexMap.get(superId).add(treeTypeTemp);
            } else {
                List<T> collectionTemp = new ArrayList<>();
                collectionTemp.add(treeTypeTemp);
                parentIdIndexMap.put(superId, collectionTemp);
            }
        }

        // 根据父ID与子节点集合的映射关系生成树状结构
        return changeToTreeByParentIdIndexMap(parentEntityList,
                maxLevelIndex,
                parentIdIndexMap);
    }


    /**
     * 将具有父子关系的实体列表转换成树结构。通过父实体列表、最大级别索引和父ID到子实体列表的映射，生成树状结构的列表。
     *
     * @param parentEntityList 父实体列表，这些实体构成了树的顶层。
     * @param maxLevelIndex 最大级别索引，用于控制树的深度。如果为负或零，则不限制深度。
     * @param parentId2childrenMap 父ID到其子实体列表的映射，用于查找特定父ID的子实体。
     * @return 转换后的树结构列表。
     */
    private static <ID extends Serializable, T extends TreeEntity<ID, T>> List<T> changeToTreeByParentIdIndexMap(
            List<T> parentEntityList, int maxLevelIndex,
            Map<ID, List<T>> parentId2childrenMap) {
        // 如果合法的节点映射得到的集合为空，则直接返回父实体列表
        if (parentId2childrenMap.size() == 0) {
            return parentEntityList;
        }

        // 将子节点放入父节点，并迭代将下一级的节点也放入
        List<T> allNextChildCollecion = setNextLevelTree(parentId2childrenMap,
                parentEntityList);

        // 开始迭代，通过不断递归设置下一级节点，直到没有下一级或达到最大级别索引
        int nowTreeIndex = 1;
        while (allNextChildCollecion != null
                && allNextChildCollecion.size() > 0) {
            // 如果指定了树的最大迭代层次，并且当前迭代级别达到或超过该值，则停止迭代
            if (maxLevelIndex > 0 && nowTreeIndex >= maxLevelIndex) {
                break;
            }
            allNextChildCollecion = setNextLevelTree(parentId2childrenMap,
                    allNextChildCollecion);
            nowTreeIndex++;
        }

        return parentEntityList; // 返回最终构建的树结构列表
    }


    /**
     * 获取下级树节点总集合
     * 将给定的父节点列表中的每个节点的子节点收集起来，并递归处理这些子节点，以构建下一级的节点集合。
     *
     * @param parentId2childrenMap 父节点ID到其子节点列表的映射。
     * @param parentEntityList 待处理的父节点列表。
     * @return 返回包含所有下级节点的列表。
     */
    private static <ID extends Serializable, T extends TreeEntity<ID, T>> List<T> setNextLevelTree(
            Map<ID, List<T>> parentId2childrenMap, List<T> parentEntityList) {
        // 初始化用于存放所有下级节点的列表
        List<T> allNextChildCollection = new ArrayList<>();
        // 遍历父节点列表，为每个父节点设置其子节点列表，并将子节点添加到下级节点总集合中
        for (T entityTemp : parentEntityList) {
            ID parentId = entityTemp.getId();
            // 如果存在子节点，则设置子节点列表，并将这些子节点添加到下级节点总集合中
            if (parentId2childrenMap.containsKey(parentId)) {
                entityTemp.setChildren(parentId2childrenMap.get(parentId));
                allNextChildCollection.addAll(parentId2childrenMap.get(parentId));
            }
        }
        // 返回下级节点总集合
        return allNextChildCollection;
    }

}
